<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div><h1 id="node-js-">Node.js中的进程</h1>
<h2 id="labdoc-header-0">一、实验介绍</h2>
<h3 id="labdoc-header-1">1.1 实验内容</h3>
<p>一个web服务器有时需要有多个进程同时运行，这就需要服务器软件具有进程管理的能力，Node.js当然也不例外。<br>本实验主要学习Node.js中的进程管理模块，涉及的模块是cluster、porcess和child_process。</p><h3 id="labdoc-header-2">1.2 实验知识点</h3>
<ul>
<li><code>process</code>模块的使用</li>
<li><code>cluster</code>模块的使用</li>
<li><code>child_process</code>模块的使用</li>
</ul>
<h3 id="labdoc-header-3">1.3 实验环境</h3>
<ul>
<li>Xfce终端</li>
<li>Node.js v4.2.1</li>
<li>gedit/vim：文本编辑</li>
</ul>
<h2 id="labdoc-header-4">二、实验步骤</h2>
<h3 id="labdoc-header-5">2.1 process模块</h3>
<p>process模块在前面的网络编程实验中已经用过了，使用时直接通过全局变量process访问，而不需要通过require方法加载。process用来和当前Node.js程序进程互动。</p><p>process是EventEmitter的一个实例，process主要包含退出事件、信号事件以及一些属性。</p><h4 id="2-1-1-exit-">2.1.1 退出事件（exit）</h4>
<p>当退出当前进程时，会促发exit事件，exit事件的回调函数中只接受同步操作，并且回调函数只接受一个参数，即退出代码，如：</p><pre><code class="hljs javascript">process.on(<span class="hljs-string"><span class="hljs-string">'exit'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">code</span></span></span><span class="hljs-function">) </span></span>{
    setTimeout(<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"></span><span class="hljs-function"><span class="hljs-params"></span>) </span></span>{
        <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'This will not run'</span></span>);
    }, <span class="hljs-number"><span class="hljs-number">0</span></span>);

    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'exit code: '</span></span>, code);
});
</code></pre><p>运行上面的代码，其中setTimeout方法中的回调函数是不会被执行的，因为exit事件中只会运行同步操作，而不会运行异步操作。</p><p>在exit事件之前还有一个beforeExit事件会被触发，在beforeExit的回调函数中可以执行异步操作。值得注意的是，通过process.exit()退出程序或者因为发生错误而退出程序是不会触发beforeExit事件的。顺便说一下，当有错误未被捕获时，就会触发uncaughtException事件。</p><h4 id="2-1-2-">2.1.2 信号事件</h4>
<p>信号事件就是接收到某个特定信号才会被触发的事件。</p><p>比如<code>SIGINT</code>事件的触发方式是<code>ctrl+c</code>：</p><pre><code class="hljs javascript"><span class="hljs-comment">// sigint.js</span>
process.<span class="hljs-built_in">stdin</span>.<span class="hljs-built_in">resume</span>();

process.on(<span class="hljs-string"><span class="hljs-string">'SIGINT'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-params"><span class="hljs-function">(<span class="hljs-params"></span>)</span></span></span><span class="hljs-function"> </span>{
    <span class="hljs-built_in">console</span>.<span class="hljs-built_in">log</span>(<span class="hljs-string"><span class="hljs-string">'Got SIGINT.  Press Control-D to exit.'</span></span>);
});
</code></pre><p>运行代码：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">sigint</span>.js
</code></pre><p>然后按住<code>ctrl</code>键，再按<code>c</code>键就会触发SIGINT事件。</p><h4 id="2-1-3-">2.1.3 属性</h4>
<p>process模块提供了很多属性，其中关于IO输入输出的主要有三个：</p><pre><code class="hljs cpp"><span class="hljs-built_in">process</span>.<span class="hljs-keyword"><span class="hljs-built_in">stdin</span></span> <span class="hljs-comment"> <span class="hljs-comment">// 标准输入</span></span>
<span class="hljs-built_in">process</span>.<span class="hljs-keyword"><span class="hljs-built_in">stdout</span></span><span class="hljs-comment"> <span class="hljs-comment">// 标准输出</span></span>
<span class="hljs-built_in">process</span>.<span class="hljs-keyword"><span class="hljs-built_in">stderr</span></span><span class="hljs-comment"> <span class="hljs-comment">// 标准错误</span></span>
</code></pre><p>举例：</p><pre><code class="hljs javascript"><span class="hljs-comment">// </span><span class="hljs-built_in"><span class="hljs-comment">stdin</span></span><span class="hljs-comment">.js</span>
process.<span class="hljs-built_in">stdin</span>.setEncoding(<span class="hljs-string"><span class="hljs-string">'utf8'</span></span>);

process.<span class="hljs-built_in">stdin</span>.on(<span class="hljs-string"><span class="hljs-string">'readable'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-params"><span class="hljs-function">(<span class="hljs-params"></span>)</span></span></span><span class="hljs-function"> </span>{
    <span class="hljs-keyword">var</span> chunk = process.<span class="hljs-built_in">stdin</span>.<span class="hljs-built_in">read</span>();
    <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (chunk !== <span class="hljs-literal">null</span>) {
        process.<span class="hljs-built_in">stdout</span>.<span class="hljs-built_in">write</span>(<span class="hljs-string"><span class="hljs-string">'data: '</span></span> + chunk);
    }
});

process.<span class="hljs-built_in">stdin</span>.on(<span class="hljs-string"><span class="hljs-string">'end'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-params"><span class="hljs-function">(<span class="hljs-params"></span>)</span></span></span><span class="hljs-function"> </span>{
    process.<span class="hljs-built_in">stdout</span>.<span class="hljs-built_in">write</span>(<span class="hljs-string"><span class="hljs-string">'end'</span></span>);
});
</code></pre><p>运行：</p><pre><code class="hljs cpp"><span class="hljs-keyword">node</span> <span class="hljs-title"><span class="hljs-built_in">stdin</span></span>.js
</code></pre><p>输入任意字符，Node.js会把输入的字符打印出来，输入<code>ctrl+D</code>触发end事件。</p><p>process还有其他属性，比如<code>process.argv</code>是一个包含了命令行参数的数组。</p><h4 id="2-1-4-">2.1.4 方法</h4>
<p>process模块还有很多实用的方法，比如：</p><pre><code class="hljs cpp">process.cwd()   <span class="hljs-regexp"><span class="hljs-comment">//</span></span><span class="hljs-comment"> 返回脚本运行工作目录</span>
process.chdir() <span class="hljs-regexp"><span class="hljs-comment">//</span></span><span class="hljs-comment"> 切换工作目录</span>
process.<span class="hljs-keyword"><span class="hljs-built_in">exit</span></span>()  <span class="hljs-regexp"><span class="hljs-comment">//</span></span><span class="hljs-comment"> 退出当前进程</span>
process.on()    <span class="hljs-regexp"><span class="hljs-comment">//</span></span><span class="hljs-comment"> 添加监听事件</span>
<span class="hljs-regexp"><span class="hljs-comment">//</span></span><span class="hljs-comment">...</span>
</code></pre><h3 id="labdoc-header-6">2.2 child_process模块</h3>
<p>child_process用于创建子进程。</p><h4 id="2-2-1-child_process-spawn-">2.2.1 child_process.spawn()方法</h4>
<p>通过当前命令启动一个新的进程。如：</p><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// test_spawn.js</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> spawn = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'child_process'</span></span>).spawn,
    ls = spawn(<span class="hljs-string"><span class="hljs-string">'ls'</span></span>, [<span class="hljs-string"><span class="hljs-string">'-lh'</span></span>, <span class="hljs-string"><span class="hljs-string">'/usr'</span></span>]);

ls.stdout.on(<span class="hljs-string"><span class="hljs-string">'data'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function"> (</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">data</span></span></span><span class="hljs-function">) </span></span>{
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'stdout: '</span></span> + data);
});

ls.stderr.on(<span class="hljs-string"><span class="hljs-string">'data'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function"> (</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">data</span></span></span><span class="hljs-function">) </span></span>{
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'stderr: '</span></span> + data);
});

ls.on(<span class="hljs-string"><span class="hljs-string">'close'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function"> (</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">code</span></span></span><span class="hljs-function">) </span></span>{
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'child process exited with code '</span></span> + code);
});
</code></pre><p>运行命令：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">test_spawn</span>.js
</code></pre><p>从结果可以看出，子进程成功运行了<code>ls -lh /usr</code>命令。</p><h4 id="2-2-2-child_process-exec-">2.2.2 child_process.exec()方法</h4>
<p>在shell中运行一个命令，并缓存其输出。如：</p><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// test_exec.js</span></span>
<span class="hljs-keyword">var</span> <span class="hljs-built_in">exec</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string"><span class="hljs-string">'child_process'</span></span>).<span class="hljs-built_in">exec</span>,
    child;

child = <span class="hljs-built_in">exec</span>(<span class="hljs-string"><span class="hljs-string">'cat *.js bad_file | wc -l'</span></span>,
    <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function"> </span><span class="hljs-params"><span class="hljs-function">(<span class="hljs-params">error, stdout, stderr</span>)</span></span><span class="hljs-function"> </span>{</span>
        <span class="hljs-built_in">console</span>.<span class="hljs-built_in">log</span>(<span class="hljs-string"><span class="hljs-string">'stdout: '</span></span> + stdout);
        <span class="hljs-built_in">console</span>.<span class="hljs-built_in">log</span>(<span class="hljs-string"><span class="hljs-string">'stderr: '</span></span> + stderr);
    <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-built_in">error</span> !== <span class="hljs-literal">null</span>) {
        <span class="hljs-built_in">console</span>.<span class="hljs-built_in">log</span>(<span class="hljs-string"><span class="hljs-string">'exec error: '</span></span> + <span class="hljs-built_in">error</span>);
    }
});
</code></pre><p>运行：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">test_exec</span>.js
</code></pre><p>因为没有bad_file 这个文件，所以会看到终端打印出了相关错误信息。</p><h4 id="2-2-3-child_process-execfile-">2.2.3 child_process.execFile()方法</h4>
<p>与exec方法类似，执行特定程序文件，参数通过一个数组传送。，如：</p><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// test_execfile.js</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> child_process = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'child_process'</span></span>);

<span class="hljs-comment"><span class="hljs-comment">// exec: spawns a shell</span></span>
child_process.exec(<span class="hljs-string"><span class="hljs-string">'ls -lh /usr'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">error, stdout, stderr</span></span></span><span class="hljs-function">)</span></span>{
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(stdout);
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'******************'</span></span>);
});

<span class="hljs-comment"><span class="hljs-comment">// execFile: executes a file with the specified arguments</span></span>
child_process.execFile(<span class="hljs-string"><span class="hljs-string">'/bin/ls'</span></span>, [<span class="hljs-string"><span class="hljs-string">'-lh'</span></span>, <span class="hljs-string"><span class="hljs-string">'/usr'</span></span>], <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">error, stdout, stderr</span></span></span><span class="hljs-function">)</span></span>{
    <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(stdout);
});
</code></pre><p>运行：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">test_execfile</span>.js
</code></pre><p>可以看出，<code>execFile</code>成功执行了<code>/bin/ls</code>程序，列出了<code>/usr</code>目录下的所有文件。</p><h4 id="2-2-4-child_process-fork-">2.2.4 child_process.fork()方法</h4>
<p>直接创建一个子进程，此进程是node命令的子进程，<code>fork('./sub.js')</code>相当于<code>spwan('node', './sub.js')</code>。fork还会在父进程与子进程之间，建立一个通信管道，通过<code>child.send()</code>发送消息。如：</p><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// main.js</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> cp = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'child_process'</span></span>);

<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> n = cp.fork(__dirname + <span class="hljs-string"><span class="hljs-string">'/sub.js'</span></span>);

n.on(<span class="hljs-string"><span class="hljs-string">'message'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">m</span></span></span><span class="hljs-function">) </span></span>{
  <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'PARENT got message:'</span></span>, m);
});

n.send({ <span class="hljs-attr"><span class="hljs-attr">hello</span></span>: <span class="hljs-string"><span class="hljs-string">'world'</span></span> });
</code></pre><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// sub.js</span></span>
process.on(<span class="hljs-string"><span class="hljs-string">'message'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">m</span></span></span><span class="hljs-function">) </span></span>{
  <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'CHILD got message:'</span></span>, m);
});

process.send({ <span class="hljs-attr"><span class="hljs-attr">foo</span></span>: <span class="hljs-string"><span class="hljs-string">'bar'</span></span> });
</code></pre><p>运行：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">main</span>.js
</code></pre><p>输出：</p><pre><code class="hljs css"><span class="hljs-selector-tag"><span class="hljs-selector-tag">PARENT</span></span> <span class="hljs-selector-tag"><span class="hljs-selector-tag">got</span></span> <span class="hljs-selector-tag"><span class="hljs-selector-tag">message</span></span>: { <span class="hljs-attribute"><span class="hljs-attribute">foo</span></span>: <span class="hljs-string"><span class="hljs-string">'bar'</span></span> }
<span class="hljs-selector-tag"><span class="hljs-selector-tag">CHILD</span></span> <span class="hljs-selector-tag"><span class="hljs-selector-tag">got</span></span> <span class="hljs-selector-tag"><span class="hljs-selector-tag">message</span></span>: { <span class="hljs-attribute"><span class="hljs-attribute">hello</span></span>: <span class="hljs-string"><span class="hljs-string">'world'</span></span> }
</code></pre><p>运行main.js会看到主进程收到了来自子进程的消息，而子进程也收到了来自主进程的消息。</p><h3 id="labdoc-header-7">2.3 cluster模块</h3>
<p>单个的Node实例运行在单个线程中。要发挥多核系统的能力，需要启动一个Node进程集群来处理负载。cluster模块就用于创建共享服务器端口的子进程。</p><p>举个例子：</p><pre><code class="hljs javascript"><span class="hljs-comment"><span class="hljs-comment">// test_cluster.js</span></span>

<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> cluster = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'cluster'</span></span>);
<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> http = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'http'</span></span>);
<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> numCPUs = <span class="hljs-built_in"><span class="hljs-built_in">require</span></span>(<span class="hljs-string"><span class="hljs-string">'os'</span></span>).cpus().length;  <span class="hljs-comment"><span class="hljs-comment">// 获取CPU内核数</span></span>

<span class="hljs-comment"><span class="hljs-comment">// master是主进程</span></span>
<span class="hljs-comment"><span class="hljs-comment">// 此处判断是否为master进程</span></span>
<span class="hljs-comment"><span class="hljs-comment">// 是则根据CPU内核数创建worker进程</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (cluster.isMaster) {
    <span class="hljs-comment"><span class="hljs-comment">// worker是运行节点</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// 根据CPU数量启动worker</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// Fork workers</span></span>
    <span class="hljs-keyword"><span class="hljs-keyword">for</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">var</span></span> i = <span class="hljs-number"><span class="hljs-number">0</span></span>; i &lt; numCPUs; i++) {
        cluster.fork();
    }

    <span class="hljs-built_in"><span class="hljs-built_in">Object</span></span>.keys(cluster.workers).forEach(<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">id</span></span></span><span class="hljs-function">) </span></span>{
        <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'I am running with ID : '</span></span> + cluster.workers[id].process.pid);
    });
    cluster.on(<span class="hljs-string"><span class="hljs-string">'exit'</span></span>, <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">worker, code, signal</span></span></span><span class="hljs-function">) </span></span>{
        <span class="hljs-built_in"><span class="hljs-built_in">console</span></span>.log(<span class="hljs-string"><span class="hljs-string">'worker '</span></span> + worker.process.pid + <span class="hljs-string"><span class="hljs-string">' died'</span></span>);
    });
} <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
    <span class="hljs-comment"><span class="hljs-comment">// cluster.isWorker == true</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// 运行到else中的代码</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// 说明当前进程是worker进程</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// 那么此worker进程就启动一个http服务</span></span>
    http.createServer(<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function">(</span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">req, res</span></span></span><span class="hljs-function">) </span></span>{
        res.writeHead(<span class="hljs-number"><span class="hljs-number">200</span></span>);
        res.end(<span class="hljs-string"><span class="hljs-string">"hello world\n"</span></span>);
    }).listen(<span class="hljs-number"><span class="hljs-number">8000</span></span>);
}
</code></pre><p>运行程序：</p><pre><code class="hljs ruby">$ <span class="hljs-keyword">node</span> <span class="hljs-title">test_cluster</span>.js
</code></pre><p>在终端会看到根据CPU内核数创建的子进程信息。</p><p>每个worker进程都是通过child_process.fork()方法产生的，所以它们可以通过IPC（进程间通信）与master进程通信。</p><p>cluster.worker是worker进程对象，其中有 <code>worker.id</code>、<code>worker.process</code>等属性，还有<code>worker.send()</code>等方法。</p><h2 id="labdoc-header-8">三、实验总结</h2>
<ol>
<li>在Node.js中，<code>Process</code>模块用于提供和程序主进程有关的功能，<code>child_process</code>用于创建子进程，<code>cluster</code>用于处理集群相关编程。</li>
</ol>
<h2 id="labdoc-header-9">四、课后作业</h2>
<p>将实验6中的作业改造成多线程下载器。</p></div>
</body>
</html>